#include "BSP/AT24Cxx/at24cxx.h"

static at24cxx_type_t g_chip = AT24C02; /* 默认 2 Kbit */

/**
 * @brief       计算器件地址
 * @param       mem_addr: 内部存储地址
 * @retval      器件地址
 */
static uint8_t calc_dev_addr(uint16_t mem_addr)
{
    uint8_t dev_addr = AT24C_BASE_ADDR << 1;

    if (g_chip >= AT24C16) /* 16Kbit 及以上需页选位 */
    {
        dev_addr |= ((mem_addr >> 11) & 0x01) << 1;
    }
    return dev_addr;
}

/**
 * @brief       获取页大小
 * @param       无
 * @retval      页大小
 */
static uint8_t get_page_size(void)
{
    return (g_chip <= AT24C02) ? 8 : 16;
}
/**
 * @brief   检查器件是否正常
 * @param   mem_addr: 内部存储地址
 * @param   data: 数据
 * @retval  0: 成功 1: 失败
 */
uint8_t at24cxx_check(void)
{
    uint8_t temp;
    uint16_t addr = 0x00;

    at24cxx_read_bytes(addr, &temp, 1); /* 避免每次开机都写AT24CXX */
    if (temp == 0x55)                   /* 读取数据正常 */
    {
        return 0;
    }
    else /* 排除第一次初始化的情况 */
    {
        temp = 0x55;
        at24cxx_write_bytes(addr, &temp, 1); /* 先写入数据 */

        at24cxx_read_bytes(addr, &temp, 1); /* 再读取数据 */

        if (temp == 0x55)
            return 0;
    }

    return 1;
}

/**
 * @brief   器件初始化
 * @param   type: 器件类型
 * @retval  0: 成功 1: 失败
 */
uint8_t at24cxx_init(at24cxx_type_t type)
{
    g_chip = type;
    return at24cxx_check();
}

/**
 * @brief       获取器件类型
 * @param       无
 * @retval      器件类型
 */
at24cxx_type_t at24cxx_get_size(void)
{
    return g_chip;
}

/**
 * @brief       跨页连续写数据
 * @param       mem_addr: 内部存储地址
 * @param       buf: 数据缓冲区
 * @param       len: 数据长度
 * @retval      AT24CXX_OK: 成功 AT24CXX_ERR_XXX: 失败  AT24CXX_ERR_ADDR: 地址错误  AT24CXX_ERR_SIZE: 长度错误
 */
at24cxx_result_t at24cxx_write_bytes(uint16_t mem_addr, const uint8_t *buf, uint16_t len)
{
    if (mem_addr + len > g_chip)
        return AT24CXX_ERR_ADDR;
    if (!buf || !len)
        return AT24CXX_ERR_SIZE;

    uint16_t page_size = get_page_size();

    while (len)
    {
        uint16_t offset = mem_addr % page_size;
        uint16_t chunk = page_size - offset;
        if (chunk > len)
            chunk = len;

        uint8_t dev = calc_dev_addr(mem_addr);

        AT24CXX_I2C_START();
        AT24CXX_I2C_SEND_BYTE(dev & 0xFE);
        if (AT24CXX_I2C_WAIT_ACK())
            goto fail;

        /* 地址高/低字节 */
        if (g_chip >= AT24C32)
        {
            AT24CXX_I2C_SEND_BYTE((mem_addr >> 8) & 0xFF);
            if (AT24CXX_I2C_WAIT_ACK())
                goto fail;
        }
        AT24CXX_I2C_SEND_BYTE(mem_addr & 0xFF);
        if (AT24CXX_I2C_WAIT_ACK())
            goto fail;

        for (uint16_t i = 0; i < chunk; i++)
        {
            AT24CXX_I2C_SEND_BYTE(buf[i]);
            if (AT24CXX_I2C_WAIT_ACK())
                goto fail;
        }
        AT24CXX_I2C_STOP();
        AT24CXX_DELAY_MS(10); // EEPROM 内部写延时

        buf += chunk;
        mem_addr += chunk;
        len -= chunk;
    }
    return AT24CXX_OK;

fail:
    AT24CXX_I2C_STOP();
    return AT24CXX_ERR_BUS;
}

/**
 * @brief       跨页连续读数据
 * @param       mem_addr: 内部存储地址
 * @param       buf: 数据缓冲区
 * @param       len: 数据长度
 * @retval      AT24CXX_OK: 成功 AT24CXX_ERR_XXX: 失败  AT24CXX_ERR_ADDR: 地址错误  AT24CXX_ERR_SIZE: 长度错误
 */
at24cxx_result_t at24cxx_read_bytes(uint16_t mem_addr, uint8_t *buf, uint16_t len)
{
    if (mem_addr + len > g_chip)
        return AT24CXX_ERR_ADDR;
    if (!buf || !len)
        return AT24CXX_ERR_SIZE;

    uint8_t dev = calc_dev_addr(mem_addr);

    /* 伪写设置地址指针 */
    AT24CXX_I2C_START();
    AT24CXX_I2C_SEND_BYTE(dev & 0xFE);
    if (AT24CXX_I2C_WAIT_ACK())
        goto fail;

    if (g_chip >= AT24C32)
    {
        AT24CXX_I2C_SEND_BYTE((mem_addr >> 8) & 0xFF);
        if (AT24CXX_I2C_WAIT_ACK())
            goto fail;
    }
    AT24CXX_I2C_SEND_BYTE(mem_addr & 0xFF);
    if (AT24CXX_I2C_WAIT_ACK())
        goto fail;

    /* 重新发起始信号进入读模式 */
    AT24CXX_I2C_START();
    AT24CXX_I2C_SEND_BYTE(dev | 0x01);
    if (AT24CXX_I2C_WAIT_ACK())
        goto fail;

    for (uint16_t i = 0; i < len; i++)
    {
        uint8_t ack = (i != len - 1); // 1 表示继续读，0 表示结束
        buf[i] = AT24CXX_I2C_READ_BYTE(ack);
    }

    AT24CXX_I2C_STOP();
    return AT24CXX_OK;

fail:
    AT24CXX_I2C_STOP();
    return AT24CXX_ERR_BUS;
}
